package cn.zwx.common.logger.advisor;

import cn.zwx.common.logger.config.AspectLogProperties;
import cn.zwx.common.logger.utils.HttpIpUtil;
import cn.zwx.common.logger.utils.JsonForGSonUtil;
import lombok.Data;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.springframework.aop.*;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;


/**
 * 拦截Feign调研
 *
 * @author 114832
 */
@Slf4j
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
@ConditionalOnProperty(prefix = "cn.zwx.logger", name = "enable", havingValue = "true", matchIfMissing = true)
public class AspectLogPointcutAdvisor extends DefaultPointcutAdvisor implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {

    private final ThreadLocal<Stack<AspectLog>> logLocal = ThreadLocal.withInitial(Stack::new);

    private static final String USER_ID = "userId";
    private static final String HOST = "Host";
    private final AspectLogProperties properties;

    public AspectLogPointcutAdvisor(@Autowired AspectLogProperties properties) {
        this.properties = properties;
        Pointcut pointcut = new BuildPointcut(properties).buildPointcut();
        setPointcut(pointcut);
        setAdvice(this);
        setOrder(-100);
    }


    @Override
    public void before(@NonNull Method method, Object[] args, Object target) {
        if (target == null) {
            return;
        }
        Stack<AspectLog> stack = logLocal.get();
        AspectLog aspectLog = new AspectLog();
        stack.push(aspectLog);
        aspectLog.beginTime = System.currentTimeMillis();
        // 获取skywalking TraceId
        aspectLog.traceId = TraceContext.traceId();
        aspectLog.traceId = StringUtils.isBlank(aspectLog.traceId) ? "N/A" : aspectLog.traceId;
        // http 请求日志
        if (AnnotationUtils.findAnnotation(target.getClass(), Controller.class) != null) {
            aspectLog.httpLog = true;
            // 请求头参数获取
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            Objects.requireNonNull(attributes);
            HttpServletRequest request = attributes.getRequest();
            aspectLog.request = request;
            aspectLog.requestURI = request.getRequestURI();
            aspectLog.heardRequestMap = this.getHeardRequestMap(request);
            //排除上传打印
            List<Object> logArgs = Arrays.stream(args)
                    .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)
                            && !(arg instanceof MultipartFile)))
                    .collect(Collectors.toList());
            try {
                log.info("请求traceId={},url={},,method={},heardRequest={}, 请求参数:{}", aspectLog.traceId, aspectLog.requestURI, request.getMethod(), aspectLog.heardRequestMap, JsonForGSonUtil.beanToJson(logArgs));
            } catch (Exception e) {
                log.error("请求参数获取异常", e);
            }
        }
    }


    @Override
    public void afterReturning(Object returnValue,@NonNull Method method, Object[] args, Object target) {
        finalProcess(returnValue, method, args, target);
    }


    public void afterThrowing(@NonNull Method method, Object[] args, Object target, Throwable ex) {

        finalProcessException(method, args, target, ex);
    }
    private void finalProcessException(Method method, Object[] args, Object target, Throwable ex) {
        Stack<AspectLog> stack = logLocal.get();
        AspectLog aspectLog = stack.pop();
        String methodName = method.getDeclaringClass().getSimpleName() + "#" + method.getName();
        if (aspectLog.httpLog) {
            try {
                //执行时长(毫秒)
                long time = System.currentTimeMillis() - aspectLog.beginTime;
                log.error("请求traceId={},方法名:{},耗时={}ms, 异常信息={}", aspectLog.traceId, methodName, time, ex.getMessage(), ex);
            } catch (Exception e) {
                log.error("请求traceId={},url={},method={},heardRequest={}", aspectLog.traceId, aspectLog.requestURI, aspectLog.request.getMethod(), aspectLog.heardRequestMap, e);
            }
        } else {
            //执行时长(毫秒)
            long time = System.currentTimeMillis() - aspectLog.beginTime;
            log.error("执行traceId={},方法名:{}请求耗时={}ms, 异常信息={}", aspectLog.traceId, methodName, time, ex.getMessage(), ex);
        }
    };


    private void finalProcess(Object result, Method method, Object[] args, Object target) {
        Stack<AspectLog> stack = logLocal.get();
        AspectLog aspectLog = stack.pop();
        if (aspectLog.httpLog) {
            try {
                //执行时长(毫秒)
                long time = System.currentTimeMillis() - aspectLog.beginTime;
                log.info("请求traceId={},耗时={}ms, 返回结果={}", aspectLog.traceId, time, JsonForGSonUtil.beanToJson(result));
            } catch (Exception e) {
                log.error("请求traceId={},url={},method={},heardRequest={}", aspectLog.traceId, aspectLog.requestURI, aspectLog.request.getMethod(), aspectLog.heardRequestMap, e);
            }
        } else {
            //执行时长(毫秒)
            long time = System.currentTimeMillis() - aspectLog.beginTime;
            if (time > properties.getLimitTime()) {
                // 耗时大于零才会打印
                log.info("执行traceId={},方法名:{}请求耗时={}ms", aspectLog.traceId, method.getDeclaringClass().getSimpleName() + "." + method.getName(), time);
            }
        }
    }

    private Map<String, Object> getHeardRequestMap(HttpServletRequest request) {
        String ip = HttpIpUtil.getIp(request);
        String userId = request.getHeader(USER_ID);
        String host = request.getHeader(HOST);
        Map<String, Object> heardRequestMap = new HashMap<>();
        if (!StringUtils.isBlank(ip)) {
            heardRequestMap.put("ip", ip);
        }
        if (!StringUtils.isBlank(host)) {
            heardRequestMap.put("host", host);
        }
        if (!StringUtils.isBlank(userId)) {
            heardRequestMap.put("userId", userId);
        }
        return heardRequestMap;
    }




    @Data
    private static class AspectLog {
        public String requestURI;
        public HttpServletRequest request;
        public Map<String, Object> heardRequestMap;
        boolean httpLog;
        Long beginTime;
        String traceId;
    }

}